package none.rg.gantt.model;


/**
 * Class representing the link between the tasks. Link could have one of
 * four types, so for its identification special identifiers are used (see below).
 * Link stores info about two tasks between which it is placed (predecessor and successor)
 * and its type.
 * @author Rodion Gorkovenko
 */
public class Link {


    private Task succ, pred;
    private Type type;
    private int hashCode;
    private Id idSucc, idPred;
    
    
    /**
     * Protected constructor. Link should not be created directly by user,
     * instead it is created via calls to {@link Chart#addLink addLink}
     */
    protected Link(Task pred, Task succ, Type type) {
        
        if (succ == null || pred == null || type == null) {
            throw new NullPointerException();
        } // if
        
        this.succ = succ;
        this.pred = pred;
        this.type = type;
        
        idSucc = fetchId(succ, type);
        idPred = fetchId(pred, type);
        
        hashCode = succ.hashCode() + 23;
        hashCode = 37 * hashCode + pred.hashCode() + 23;
        hashCode = 37 * hashCode + type.hashCode() + 23;
    } // Link
    
    
    /** Returns successor task */
    public Task getSucc() { return succ; }
    
    /** Returns predecessor task */
    public Task getPred() { return pred; }
    
    /** Returns type of the link */
    public Type getType() { return type; }
    
    /** Returns Link identifier for the predecessor's internal map */
    public Id getIdPred() { return idPred; }
    
    /** Returns Link identifier for the successor's internal map */
    public Id getIdSucc() { return idSucc; }
    
    
    /** Removes this link (notifying both task so they could delete it from their maps */
    public void remove() {
        pred.notifyRemoveSucc(this, false);
        succ.notifyRemovePred(this, true);
        succ = null;
        pred = null;
    } // remove
    
    
    /** Returns link identifier for specified task and link type */
    public static Id fetchId(Task task, Type type) {
        // this may be later improved with cache etc.
        return new Id(task, type);
    } // fetchId
    
    
    /**
     * Checks equality of two links (based on both tasks ids and link type)
     */
    @Override
    public boolean equals(Object o) {
        Link link;
        
        if (!this.getClass().isInstance(o)) {
            return false;
        } // if
        
        link = (Link) o;
        return link.hashCode() == hashCode && link.getSucc().equals(succ)
                && link.getPred().equals(pred) && link.getType().equals(type);
    } // equals
    
    
    /** Returns hash code calculated when link was created */
    @Override
    public int hashCode() {
        return hashCode;
    } // hashCode
    
    
    /** Returns string representation of a link for test purposes */
    @Override
    public String toString() {
        return String.format("Link(id-%1$d:id-%2$d, %3$s)",
                pred.getId(), succ.getId(), type);
    } // toString

    
    /** Enum of link types */
    public static enum Type {
        FINISH_START, START_START, FINISH_FINISH, START_FINISH
    } // enum Type
    
    
    /**
     * Link identifier class (for storing links in the internal maps of tasks)
     */
    public static class Id {
        
        int taskId;
        Type type;
        int hashCode;
        
        private Id(Task task, Type type) {
            this.taskId = task.getId();
            this.type = type;
            hashCode = taskId * 3 + type.hashCode() * 7;
        } // Id
        
        public int getTaskId() { return taskId; }
        
        public Type getType() { return type; }
        
        @Override
        public boolean equals(Object o) {
            Id other;
            
            try {
                other = (Id) o;
            } catch (ClassCastException e) {
                return false;
            } // catch
            
            return other.getTaskId() == taskId && other.getType() == type;
        } // equals
        
        @Override
        public int hashCode() {
            return hashCode;
        } // hashCode
        
    } // class Id
    
    
} // class Link

